/*
 * Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include "virtnet_util.h"
#include "virtnet_rpc.h"
#include "virtnet_controller.h"

#define MAX_STRING_LEN		(20)
/* For string conversion */
char string[MAX_STRING_LEN];

enum
{
	MODIFY_SUBCMD_DEVICE = 0,
	MODIFY_SUBCMD_QUEUE,
};

#define JSON_STR_NUM_TO_OBJ(obj, str, format, num) do { \
	memset(string, 0, MAX_STRING_LEN); \
	snprintf(string, MAX_STRING_LEN, format, num); \
	cJSON_AddStringToObject(obj, str, string); } while(0) \

#define JSON_NUM_STR_TO_OBJ(obj, num, format, str) do { \
	memset(string, 0, MAX_STRING_LEN); \
	snprintf(string, MAX_STRING_LEN, format, num); \
	cJSON_AddStringToObject(obj, string, str); } while(0) \

#define JSON_2NUM_STR_TO_OBJ(obj, num1, num2, format, str) do { \
	memset(string, 0, MAX_STRING_LEN); \
	snprintf(string, MAX_STRING_LEN, format, num1, num2); \
	cJSON_AddStringToObject(obj, string, str); } while(0) \

static void get_dev_info(cJSON *device,
			 struct virtnet_device *dev,
			 struct virtnet_device *parent_dev)
{
	if (parent_dev) {
		cJSON_AddNumberToObject(device, "vf_id", dev->id);
		cJSON_AddNumberToObject(device, "parent_pf_id",
					parent_dev->id);
	} else {
		cJSON_AddNumberToObject(device, "pf_id", dev->id);
	}
	cJSON_AddStringToObject(device, "function_type",
				dev->flag & VIRTNET_DEV_PF ? "PF" : "VF");
	if (dev->flag & VIRTNET_DEV_PF)
		cJSON_AddStringToObject(device, "device_type",
					dev->flag & VIRTNET_DEV_PRE_CONFIG ?
					"static" : "hotplug");
	virtnet_get_bdf(dev, string, MAX_STRING_LEN);
	cJSON_AddStringToObject(device, "bdf", string);
	cJSON_AddStringToObject(device, "sf_parent_device",
				dev->port_ctx->dev->name);
	mlxdevm_port_netdev_get(dev->port_ctx->devm, dev->sf_verbs.port,
				dev->sf_verbs.rep_ndev_name);
	cJSON_AddStringToObject(device, "sf_rep_name",
				dev->sf_verbs.rep_ndev_name);
	JSON_STR_NUM_TO_OBJ(device, "msix_config_vector", "0x%X",
			    dev->registers->vattr.msix_config);
	cJSON_AddNumberToObject(device, "num_msix",
				dev->snap_pci->pci_attr.num_msix);
	cJSON_AddNumberToObject(device, "max_queues",
				dev->registers->vattr.max_queues);
	cJSON_AddNumberToObject(device, "queues_size",
				dev->registers->vattr.max_queue_size);
	virtnet_get_mac(dev, string, MAX_STRING_LEN);
	cJSON_AddStringToObject(device, "net_mac", string);
	cJSON_AddNumberToObject(device, "net_mtu", dev->registers->mtu);

	if (dev->flag & VIRTNET_DEV_PF && dev->num_of_vfs)
		cJSON_AddNumberToObject(device, "num_vfs", dev->num_of_vfs);
}

static void get_features_info(cJSON *obj, uint64_t features)
{
	int i;

	for (i = 0; i < 64; ++i) {
		if (features & (1ULL << i))
			JSON_NUM_STR_TO_OBJ(obj, i, "%8d", feature_names[i]);
	}
}

static void get_status_info(cJSON *obj,  uint16_t status)
{
	int i;

	for (i = 0; i < STATUS_ID_SIZE; ++i) {
		JSON_2NUM_STR_TO_OBJ(obj, (status & status_id[i]) ? 'x' : ' ',
				   status_id[i], "  [%c] %3d", status_names[i]);
	}
}

static void get_dev_query(cJSON *device,
			 struct virtnet_device *dev,
			 struct virtnet_device *parent_dev)
{
	struct snap_virtio_queue_counters_attr q_cnt = {0};
	struct snap_virtio_net_queue *snap_q;
	int i;

	if (parent_dev) {
		cJSON_AddNumberToObject(device, "vf_id", dev->id);
		cJSON_AddNumberToObject(device, "parent_pf_id",
					parent_dev->id);
	} else {
		cJSON_AddNumberToObject(device, "pf_id", dev->id);
	}

	virtnet_get_bdf(dev, string, MAX_STRING_LEN);
	cJSON_AddStringToObject(device, "pci_bdf", string);

	JSON_STR_NUM_TO_OBJ(device, "pci_dev_id", "0x%hx",
			    dev->snap_pci->pci_attr.device_id);
	JSON_STR_NUM_TO_OBJ(device, "pci_vendor_id", "0x%hx",
			    dev->snap_pci->pci_attr.vendor_id);
	JSON_STR_NUM_TO_OBJ(device, "pci_class_code", "0x%x",
			    dev->snap_pci->pci_attr.class_code);
	JSON_STR_NUM_TO_OBJ(device, "pci_subsys_id", "0x%hx",
			    dev->snap_pci->pci_attr.subsystem_id);
	JSON_STR_NUM_TO_OBJ(device, "pci_subsys_vendor_id", "0x%hx",
			    dev->snap_pci->pci_attr.subsystem_vendor_id);
	JSON_STR_NUM_TO_OBJ(device, "pci_revision_id", "%hhu",
			    dev->snap_pci->pci_attr.revision_id);
	JSON_STR_NUM_TO_OBJ(device, "pci_max_vfs", "%d",
			    dev->snap_pci->num_vfs);
	JSON_STR_NUM_TO_OBJ(device, "enabled_vfs", "%d",
			    dev->num_of_vfs);
	JSON_STR_NUM_TO_OBJ(device, "device_feature", "0x%lx",
			    dev->registers->vattr.device_feature);
	get_features_info(device, dev->registers->vattr.device_feature);
	JSON_STR_NUM_TO_OBJ(device, "driver_feature", "0x%lx",
			    dev->registers->vattr.driver_feature);
	get_features_info(device, dev->registers->vattr.driver_feature);
	JSON_STR_NUM_TO_OBJ(device, "msix_config_vector", "0x%X",
			    dev->registers->vattr.msix_config);
	JSON_STR_NUM_TO_OBJ(device, "num_msix", "%hu",
			    dev->snap_pci->pci_attr.num_msix);
	JSON_STR_NUM_TO_OBJ(device, "status", "%d",
			    dev->registers->vattr.status);
	get_status_info(device, dev->registers->vattr.status);
	JSON_STR_NUM_TO_OBJ(device, "pci_vhca_id", "0x%X",
			    dev->snap_pci->mpci.vhca_id);
	JSON_STR_NUM_TO_OBJ(device, "enabled", "%d",
			    dev->registers->vattr.enabled);
	JSON_STR_NUM_TO_OBJ(device, "net_link_status", "%hu",
			    dev->registers->status);
	virtnet_get_mac(dev, string, MAX_STRING_LEN);
	cJSON_AddStringToObject(device, "net_mac", string);
	JSON_STR_NUM_TO_OBJ(device, "net_mac_queue_pairs", "%hu",
			    dev->registers->max_queue_pairs);
	JSON_STR_NUM_TO_OBJ(device, "net_mtu", "%hu",
			    dev->registers->mtu);

	cJSON_AddStringToObject(device, "sf_rdma_device",
				dev->sf_verbs.rdma_dev_name);
	mlxdevm_port_netdev_get(dev->port_ctx->devm, dev->sf_verbs.port,
				dev->sf_verbs.rep_ndev_name);
	cJSON_AddStringToObject(device, "sf_rep_netdev",
				dev->sf_verbs.rep_ndev_name);
	JSON_STR_NUM_TO_OBJ(device, "sf_vhca_id", "0x%X",
			    dev->sf_verbs.vhca_id);

	JSON_STR_NUM_TO_OBJ(device, "max_queues", "%hu",
			    dev->registers->vattr.max_queues);
	JSON_STR_NUM_TO_OBJ(device, "enabled_queues", "%lu",
			    dev->snap_ctrl->common.enabled_queues);

	cJSON *queues = cJSON_CreateArray();
	cJSON *queue;
	for (i = 0; i < dev->num_eth_queues + 1; i++) {
		queue = cJSON_CreateObject();;
		if (i == dev->num_eth_queues)
			snap_q = dev->ctrl_vq.snap_q;
		else
			snap_q = dev->eth_vqs[i].snap_q;
		snap_virtio_net_query_counters(snap_q, &q_cnt);
		JSON_STR_NUM_TO_OBJ(queue, "id", "%hu",
				dev->reg_vqs[i].vattr.idx);
		JSON_STR_NUM_TO_OBJ(queue, "size", "%hu",
				dev->reg_vqs[i].vattr.size);
		JSON_STR_NUM_TO_OBJ(queue, "msix_vector", "0x%hx",
				dev->reg_vqs[i].vattr.msix_vector);
		JSON_STR_NUM_TO_OBJ(queue, "enable", "%hu",
				dev->reg_vqs[i].vattr.enable);
		JSON_STR_NUM_TO_OBJ(queue, "notify_offset", "%hu",
				dev->reg_vqs[i].vattr.notify_off);
		JSON_STR_NUM_TO_OBJ(queue, "descriptor_address",
				"0x%lx", dev->reg_vqs[i].vattr.desc);
		JSON_STR_NUM_TO_OBJ(queue, "driver_address",
				"0x%lx", dev->reg_vqs[i].vattr.driver);
		JSON_STR_NUM_TO_OBJ(queue, "device_address",
				"0x%lx", dev->reg_vqs[i].vattr.device);
		JSON_STR_NUM_TO_OBJ(queue, "mkey", "0x%x",
				dev->reg_vqs[i].vattr.dma_mkey);
		JSON_STR_NUM_TO_OBJ(queue, "received_desc", "%lu",
				    q_cnt.received_desc);
		JSON_STR_NUM_TO_OBJ(queue, "completed_desc", "%lu",
				    q_cnt.completed_desc);
		JSON_STR_NUM_TO_OBJ(queue, "bad_desc_errors", "%u",
				    q_cnt.bad_desc_errors);
		JSON_STR_NUM_TO_OBJ(queue, "error_cqes", "%u",
				    q_cnt.error_cqes);
		JSON_STR_NUM_TO_OBJ(queue, "exceed_max_chain", "%u",
				q_cnt.exceed_max_chain);
		JSON_STR_NUM_TO_OBJ(queue, "invalid_buffer", "%u",
				q_cnt.invalid_buffer);

		cJSON_AddItemToArray(queues, queue);
	}
	cJSON_AddItemToObject(device, "enabled-queues-info", queues);
}

cJSON *query(jrpc_context *ctx, cJSON *params, cJSON *id)
{
	struct virtnet_rpc_context *rpc_ctx;
	struct virtnet_context *virtnet_ctx;
	struct virtnet_device *dev;
	int pf_id, vf_id;
	cJSON *device;
	int i, j;

	rpc_ctx = (struct virtnet_rpc_context *)ctx->data;
	virtnet_ctx = rpc_ctx->virtnet_ctx;

	cJSON *all = cJSON_GetObjectItem(params, "all");
	cJSON *pf = cJSON_GetObjectItem(params, "pf");
	cJSON *vf = cJSON_GetObjectItem(params, "vf");

	cJSON *result = cJSON_CreateObject();
	cJSON *devices = cJSON_CreateArray();

	pthread_mutex_lock(&rpc_ctx->rpc_lock);

	if (all->valueint) {
		for (i = 0; i < virtnet_ctx->sctx->virtio_net_pfs.max_pfs; i++) {
			dev = &virtnet_ctx->dev_list[i];
			if (!(dev && dev->snap_pci))
				continue;

			device = cJSON_CreateObject();
			get_dev_query(device, dev, NULL);
			cJSON_AddItemToArray(devices, device);

			if ((dev->flag & VIRTNET_DEV_PF) && dev->num_of_vfs) {
				for (j = 0; j < dev->num_of_vfs; j++) {
					device = cJSON_CreateObject();
					get_dev_query(device, &dev->vf_dev[j], dev);
					cJSON_AddItemToArray(devices, device);
				}
			}
		}

		cJSON_AddItemToObject(result, "devices", devices);
		goto unlock;
	}

	if (pf) {
		pf_id = pf->valueint;
		if (pf_id >= virtnet_ctx->sctx->virtio_net_pfs.max_pfs) {
			cJSON_AddStringToObject(result, "Error",
					"Device doesn't exist");
			goto unlock;
		}

		dev = &virtnet_ctx->dev_list[pf_id];
		if (!(dev && dev->snap_pci)) {
			cJSON_AddStringToObject(result, "Error",
					"Device is not opened");
			goto unlock;
		}

		if (!vf) {
			device = cJSON_CreateObject();
			get_dev_query(device, dev, NULL);
			cJSON_AddItemToArray(devices, device);
		} else {
			vf_id = vf->valueint;
			if (vf_id >= dev->num_of_vfs) {
				cJSON_AddStringToObject(result, "Error",
						"Device doesn't exist");
				goto unlock;
			}
			if ((dev->flag & VIRTNET_DEV_PF) && dev->num_of_vfs) {
				device = cJSON_CreateObject();
				get_dev_query(device, &dev->vf_dev[vf_id], dev);
				cJSON_AddItemToArray(devices, device);
			}
		}

		cJSON_AddItemToObject(result, "devices", devices);
		goto unlock;
	}

	if (vf) {
		cJSON_AddStringToObject(result, "Error",
					"-v needs to be used with -p!");
		goto unlock;
	}

	cJSON_AddStringToObject(result, "Error", "-a or -p is needed!");

unlock:
	pthread_mutex_unlock(&rpc_ctx->rpc_lock);
	return result;
}

cJSON *list(jrpc_context *ctx, cJSON *params, cJSON *id)
{
	struct virtnet_rpc_context *rpc_ctx;
	struct virtnet_context *virtnet_ctx;
	struct virtnet_device *dev;
	cJSON *device;
	int i, j;

	rpc_ctx = (struct virtnet_rpc_context *)ctx->data;
	virtnet_ctx = rpc_ctx->virtnet_ctx;

	cJSON *result = cJSON_CreateObject();
	cJSON *controller = cJSON_CreateObject();
	cJSON *devices = cJSON_CreateArray();

	pthread_mutex_lock(&rpc_ctx->rpc_lock);

	if (virtnet_ctx->sctx)
		cJSON_AddStringToObject(controller,
					"virtio-net_emulation_manager",
				      virtnet_ctx->sctx->context->device->name);
	JSON_STR_NUM_TO_OBJ(controller, "hot_plug_max_hotplug_devices", "%hhu",
			    virtnet_ctx->sctx->hotplug.max_devices);
	JSON_STR_NUM_TO_OBJ(controller, "event_modes", "0x%x",
			    virtnet_ctx->sctx->virtio_net_caps.event_modes);
	JSON_STR_NUM_TO_OBJ(controller, "supported_features", "0x%lx",
			    virtnet_ctx->max_features);
	get_features_info(controller, virtnet_ctx->max_features);
	JSON_STR_NUM_TO_OBJ(controller, "max_virtionet_devices", "%d",
			    virtnet_ctx->sctx->virtio_net_pfs.max_pfs);
	JSON_STR_NUM_TO_OBJ(controller, "max_virtq", "%u",
			virtnet_ctx->sctx->virtio_net_caps.max_emulated_virtqs);
	JSON_STR_NUM_TO_OBJ(controller, "max_tunnel_descriptors", "%hu",
			    virtnet_ctx->sctx->virtio_net_caps.max_tunnel_desc);
	JSON_STR_NUM_TO_OBJ(controller, "supported_virtq_types", "%u",
			    virtnet_ctx->sctx->virtio_net_caps.supported_types);

	for (i = 0; i < virtnet_ctx->sctx->virtio_net_pfs.max_pfs; i++) {
		dev = &virtnet_ctx->dev_list[i];
		if (!(dev && dev->snap_pci))
			continue;

		device = cJSON_CreateObject();
		get_dev_info(device, dev, NULL);
		cJSON_AddItemToArray(devices, device);

		if ((dev->flag & VIRTNET_DEV_PF) && dev->num_of_vfs) {
			for (j = 0; j < dev->num_of_vfs; j++) {
				device = cJSON_CreateObject();
				get_dev_info(device, &dev->vf_dev[j], dev);
				cJSON_AddItemToArray(devices, device);
			}
		}
	}
	pthread_mutex_unlock(&rpc_ctx->rpc_lock);

	cJSON_AddItemToObject(result, "controller", controller);
	cJSON_AddItemToObject(result, "devices", devices);
	return result;
}

cJSON *unplug_result(int ret)
{
	cJSON *result = cJSON_CreateObject();
	if (ret) {
		cJSON_AddNumberToObject(result, "ret", ret);
		return result;
	}

	return cJSON_CreateString("SUCCEED");
}

cJSON *unplug(jrpc_context *ctx, cJSON *params, cJSON *id)
{
	struct virtnet_rpc_context *rpc_ctx;
	struct virtnet_context *virtnet_ctx;
	struct virtnet_device *dev;
	int ret = 0;

	rpc_ctx = (struct virtnet_rpc_context *)ctx->data;
	virtnet_ctx = rpc_ctx->virtnet_ctx;

	pthread_mutex_lock(&rpc_ctx->rpc_lock);

	cJSON *dev_id = cJSON_GetObjectItem(params, "id");
	if (id->valueint >= virtnet_ctx->sctx->virtio_net_pfs.max_pfs) {
		ret = ENODEV;
		goto unlock;
	}

	dev = &virtnet_ctx->dev_list[dev_id->valueint];

	if (!dev->snap_pci || !dev->snap_pci->hotplugged) {
		ret = EIO;
		goto unlock;
	}

	virtnet_device_unplug(dev);

unlock:
	pthread_mutex_unlock(&rpc_ctx->rpc_lock);
	return unplug_result(ret);
}

cJSON *hotplug_result(struct virtnet_device *dev, int ret)
{
	cJSON *result = cJSON_CreateObject();

	if (ret)
		return cJSON_CreateString("FAILED");

	virtnet_get_bdf(dev, string, MAX_STRING_LEN);
	cJSON_AddStringToObject(result, "bdf", string);

	cJSON_AddNumberToObject(result, "id", dev->id);
	mlxdevm_port_netdev_get(dev->port_ctx->devm, dev->sf_verbs.port,
				dev->sf_verbs.rep_ndev_name);
	cJSON_AddStringToObject(result, "rep_name",
				dev->sf_verbs.rep_ndev_name);

	virtnet_get_mac(dev, string, MAX_STRING_LEN);
	cJSON_AddStringToObject(result, "mac", string);

	return result;
}

cJSON *hotplug(jrpc_context *ctx, cJSON *params, cJSON *id)
{
	struct virtnet_device_attr attr = {};
	struct virtnet_rpc_context *rpc_ctx;
	struct virtnet_context *virtnet_ctx;
	struct virtnet_device *dev = NULL;
	struct ibv_device *ib_dev;
	uint8_t *mac_octet;
	int ret = 0;

	rpc_ctx = (struct virtnet_rpc_context *)ctx->data;
	virtnet_ctx = rpc_ctx->virtnet_ctx;

	cJSON *ib_name = cJSON_GetObjectItem(params, "ib_device");
	cJSON *features = cJSON_GetObjectItem(params, "features");
	cJSON *mac = cJSON_GetObjectItem(params, "mac");
	cJSON *mtu = cJSON_GetObjectItem(params, "mtu");
	cJSON *num_queues = cJSON_GetObjectItem(params, "num_queues");
	cJSON *queue_size = cJSON_GetObjectItem(params, "queue_size");

	ib_dev = virtnet_get_ib_dev(ib_name->valuestring);
	if (!ib_dev) {
		ret = ENODEV;
		goto out;
	}
	attr.ib_dev = ib_dev;

	if (features) {
		sscanf(features->valuestring, "0x%lx", &attr.features);
		log_debug("features: 0x%lx", attr.features);
	}

	attr.mtu = mtu->valueint;
	attr.num_queues = num_queues->valueint;
	attr.queue_size = queue_size->valueint;

	mac_octet = (uint8_t *)&attr.mac.ether_addr_octet;
	ret = sscanf(mac->valuestring,
		     "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
		     &mac_octet[0], &mac_octet[1], &mac_octet[2],
		     &mac_octet[3], &mac_octet[4], &mac_octet[5]);

	if (ret != ETH_ALEN) {
		log_error("Invalid ethernet address: %s", mac->valuestring);
		ret = -EINVAL;
		goto out;
	}

	pthread_mutex_lock(&rpc_ctx->rpc_lock);
	dev = virtnet_device_hotplug(virtnet_ctx, &attr);
	pthread_mutex_unlock(&rpc_ctx->rpc_lock);
	if (!dev) {
		log_error("Hotplug virtio device failed");
		ret = -ENOENT;
	} else {
		ret = 0;
	}

out:
	return hotplug_result(dev, ret);
}

static int _modify_device(cJSON *params, struct virtnet_device *dev,
			  struct virtnet_rpc_context *rpc_ctx)
{
	struct virtnet_device_modify_fields val = {};
	int ret = 0, modify_flags = 0;
	uint8_t *mac_octet;

	cJSON *state = cJSON_GetObjectItem(params, "state");
	cJSON *features = cJSON_GetObjectItem(params, "features");
	cJSON *mac = cJSON_GetObjectItem(params, "mac");
	cJSON *mtu = cJSON_GetObjectItem(params, "mtu");
	cJSON *link = cJSON_GetObjectItem(params, "link");
	cJSON *rx_mode = cJSON_GetObjectItem(params, "rx_mode");

	if (!(dev->flag & VIRTNET_DEV_STARTED) && (state || link || rx_mode)) {
		log_error("device needs to be started while "
			  "trying to modify state, link, rxmode");
		ret = -ENODEV;
		goto out;
	}

	if (state) {
		val.state = state->valueint;
		modify_flags |= VIRTNET_MODIFY_STATE;
	}

	if (features) {
		sscanf(features->valuestring, "0x%lx", &val.features);
		log_debug("Change to feature: 0x%lx", val.features);
		modify_flags |= VIRTNET_MODIFY_FEATURES;
	}

	if (mtu) {
		val.mtu = mtu->valueint;
		modify_flags |= VIRTNET_MODIFY_MTU;
	}

	if (link) {
		val.link_up = link->valueint;
		modify_flags |= VIRTNET_MODIFY_LINK;
	}

	if (mac) {
		mac_octet = (uint8_t *)&val.mac.ether_addr_octet;
		ret = sscanf(mac->valuestring,
			     "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
			     &mac_octet[0], &mac_octet[1], &mac_octet[2],
			     &mac_octet[3], &mac_octet[4], &mac_octet[5]);

		if (ret != ETH_ALEN) {
			log_error("Invalid ethernet address: %s",
				  mac->valuestring);
			ret = -EINVAL;
			goto out;
		}
		modify_flags |= VIRTNET_MODIFY_MAC;
	}

	if (rx_mode) {
		sscanf(rx_mode->valuestring, "0x%x", &dev->rx_mode.val);
		log_debug("RX mode: 0x%x", dev->rx_mode.val);
		pthread_mutex_lock(&rpc_ctx->rpc_lock);
		ret = virtnet_sf_set_rx_mode(dev);
		pthread_mutex_unlock(&rpc_ctx->rpc_lock);
		ret = virtnet_device_rxmode_save(dev, dev->rx_mode.val);
		if (ret) {
			log_error("Failed to save rx_mode to recovery");
			goto out;
		}
		goto out;
	}

	pthread_mutex_lock(&rpc_ctx->rpc_lock);
	pthread_mutex_lock(&dev->lock);
	ret = virtnet_device_modify(dev, &val, modify_flags);
	pthread_mutex_unlock(&dev->lock);
	pthread_mutex_unlock(&rpc_ctx->rpc_lock);
	if (ret)
		log_error("Failed to modify device");

	if (mac) {
		ret = virtnet_device_mac_save(dev, mac_octet);
		if (ret)
			goto out;
	}
out:
	return ret;
}

static enum queue_period_mode _parse_period_mode(char *period_mode)
{
	enum queue_period_mode parsed = VIRTQ_PERIOD_DEFAULT;

	if (!strcmp("event", period_mode))
		parsed = VIRTQ_PERIOD_UPON_EVENT;
	else if (!strcmp("cqe", period_mode))
		parsed = VIRTQ_PERIOD_UPON_CQE;

	return parsed;
}

static int _modify_queue(cJSON *params, struct virtnet_device *dev,
			  struct virtnet_rpc_context *rpc_ctx)
{
	int ret = 0;

	cJSON *period_mode = cJSON_GetObjectItem(params, "period_mode");
	cJSON *period = cJSON_GetObjectItem(params, "period");
	cJSON *max_count = cJSON_GetObjectItem(params, "max_count");

	if (!(dev->flag & VIRTNET_DEV_STARTED)) {
		log_error("device needs to be started while trying to modify queue");
		ret = -ENODEV;
		goto out;
	}

	if (period_mode) {
		dev->period.mode = _parse_period_mode(period_mode->valuestring);
		if (dev->period.mode != VIRTQ_PERIOD_DEFAULT) {
			dev->period.period =
				period ? period->valueint : dev->period.period;
			dev->period.max_count =
			max_count ? max_count->valueint : dev->period.max_count;
			pthread_mutex_lock(&rpc_ctx->rpc_lock);
			ret = virtnet_vqs_update_period(dev);
			pthread_mutex_unlock(&rpc_ctx->rpc_lock);
		}
		goto out;
	}

out:
	return ret;
}

cJSON *modify(jrpc_context *ctx, cJSON *params, cJSON *id)
{
	struct virtnet_rpc_context *rpc_ctx;
	struct virtnet_context *virtnet_ctx;
	struct virtnet_device *dev;
	int pf_id, vf_id;
	int ret = 0;

	rpc_ctx = (struct virtnet_rpc_context *)ctx->data;
	virtnet_ctx = rpc_ctx->virtnet_ctx;

	cJSON *pf = cJSON_GetObjectItem(params, "pf");
	cJSON *vf = cJSON_GetObjectItem(params, "vf");

	pf_id = pf->valueint;
	if (pf_id >= virtnet_ctx->sctx->virtio_net_pfs.max_pfs) {
		ret = -ENODEV;
		goto out;
	}

	dev = &virtnet_ctx->dev_list[pf_id];
	if (vf) {
		vf_id = vf->valueint;
		dev = &dev->vf_dev[vf_id];
	}

	if (!dev)
		return cJSON_CreateString("Failed: device not found");

	cJSON *subcmd = cJSON_GetObjectItem(params, "subcmd");
	if (subcmd->valueint == MODIFY_SUBCMD_DEVICE)
		ret = _modify_device(params, dev, rpc_ctx);
	else if (subcmd->valueint == MODIFY_SUBCMD_QUEUE)
		ret = _modify_queue(params, dev, rpc_ctx);

out:
	return cJSON_CreateString(ret ? "FAILED" : "SUCCEED");
}

cJSON *log_level(jrpc_context *ctx, cJSON *params, cJSON *id)
{
	cJSON *level = cJSON_GetObjectItem(params, "level");
        int val = 0;

        if (!strcmp("info", level->valuestring))
                val = LOG_INFO;
        else if (!strcmp("err", level->valuestring))
                val = LOG_ERR;
        else if (!strcmp("debug", level->valuestring))
                val = LOG_DEBUG;
        log_info("Set log level to %s", level->valuestring);

        virtnet_set_log_level(val);

        return cJSON_CreateString("SUCCEED");
}

static void *virtnet_rpc_handler(void *ctx)
{
	struct virtnet_rpc_context *rpc_ctx;

	rpc_ctx = (struct virtnet_rpc_context *)ctx;

	jrpc_server_init(&rpc_ctx->rpc_server, RPC_PORT);

	jrpc_register_procedure(&rpc_ctx->rpc_server, hotplug, "hotplug",
				ctx);
	jrpc_register_procedure(&rpc_ctx->rpc_server, unplug, "unplug",
				ctx);
	jrpc_register_procedure(&rpc_ctx->rpc_server, list, "list",
				ctx);
	jrpc_register_procedure(&rpc_ctx->rpc_server, query, "query",
				ctx);
	jrpc_register_procedure(&rpc_ctx->rpc_server, modify,
				"modify", ctx);
	jrpc_register_procedure(&rpc_ctx->rpc_server, log_level,
				"log_level", ctx);

	jrpc_server_run(&rpc_ctx->rpc_server);
	pthread_exit(NULL);
}

int virtnet_rpc_start(struct virtnet_rpc_context *rpc_ctx,
		      struct virtnet_context *virtnet_ctx)
{
	pthread_attr_t pattr = {};
	int rc;

	pthread_attr_init(&pattr);
	pthread_attr_setdetachstate(&pattr, PTHREAD_CREATE_JOINABLE);
	rpc_ctx->virtnet_ctx = virtnet_ctx;

	rc = pthread_create(&rpc_ctx->rpc_server_tid, &pattr,
			    virtnet_rpc_handler, rpc_ctx);
	if (rc)
		log_error("Failed to create RPC server thread");

	pthread_mutex_init(&rpc_ctx->rpc_lock, NULL);

	return rc;
}

void virtnet_rpc_stop(struct virtnet_rpc_context *rpc_ctx)
{
	pthread_mutex_destroy(&rpc_ctx->rpc_lock);
	pthread_cancel(rpc_ctx->rpc_server_tid);
	jrpc_server_stop(&rpc_ctx->rpc_server);
}
